using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml;
using XmlObjects.XmlDocumentMarkup;

namespace XmlObjects
{
	public class ElementPropertyReader : DocumentPropertyReader
	{
		private XmlElementAttribute attribute;

		public ElementPropertyReader(PropertyInfo info)
			: base(info)
		{
			attribute = GetAttribute<XmlElementAttribute>(info);
		}

		public override void WriteXml(XmlNode node, DocumentElement theObject, XmlDocument ownerDocument)
		{
			if (node.SelectSingleNode(attribute.Name) != null)
			{
				node.RemoveChild(node.SelectSingleNode(attribute.Name));
			}
			DocumentElement element = propertyInfo.GetValue(theObject, null) as DocumentElement;
			XmlElement newElement = ownerDocument.CreateElement(attribute.Name);
			node.AppendChild(newElement);
			if (element != null)
			{
				IList<SuperClassAttribute> superClassAttributes = AttributeHelper.GetAttributes<SuperClassAttribute>(element.GetType(), true);
				if (superClassAttributes.Count>0)
				{
					string attributeName = superClassAttributes[0].AttributeName;
					foreach (SubClassAttribute classAttribute in AttributeHelper.GetAttributes<SubClassAttribute>(element.GetType(), true))
					{
						if (element.GetType() == classAttribute.SubClassType)
						{
							string attributeValue = classAttribute.Id;
							XmlAttribute newAttribute = ownerDocument.CreateAttribute(attributeName);
							newAttribute.Value = attributeValue;
							newElement.Attributes.Append(newAttribute);
							break;
						}
					}
				}
				element.AppendTo(attribute.Name, node, ownerDocument);
			}
		}

		public override
			void ReadXml(XmlNode node, object theObject)
		{
			XmlNode myNode = node.SelectSingleNode(attribute.Name);
			DocumentElement element = null;
			if (myNode != null)
			{
				Type theType = GetTypeOfObjectRepresentedBy(node);
				element = (DocumentElement) Activator.CreateInstance(theType);
				element.ReadFrom(myNode);
			}
			propertyInfo.SetValue(theObject, element, null);
		}

		

		private
			Type GetTypeOfObjectRepresentedBy(XmlNode node)
		{
			object[] objects = propertyInfo.PropertyType.GetCustomAttributes(typeof (SuperClassAttribute), true);
			if (null == objects || objects.Length == 0)
				return propertyInfo.PropertyType;

			SuperClassAttribute superClassAttribute = (SuperClassAttribute) objects[0];
			XmlAttribute xmlAttribute = node.Attributes[superClassAttribute.AttributeName];
			if (xmlAttribute == null)
			{
				if (superClassAttribute.DefaultType != null)
					return superClassAttribute.DefaultType;
				else
					throw new Exception(
						string.Format(
							"Attribute name '{0}' must be specified on the '{1}' node, or else DefaultValue must be specified on the superclass attribute of type '{2}'.",
							superClassAttribute.AttributeName, node.Name, propertyInfo.PropertyType.Name)
						);
			}

			string attributeValue = xmlAttribute.Value;
			foreach (
				SubClassAttribute classAttribute in
					AttributeHelper.GetAttributes<SubClassAttribute>(propertyInfo.PropertyType, true))
			{
				if (classAttribute.Id.Equals(attributeValue))
					return classAttribute.SubClassType;
			}
			throw new Exception(string.Format("Didn't find a subclass attribute on type '{0}' matching '{1}'='{2}'",
			                                  propertyInfo.PropertyType.Name, superClassAttribute.AttributeName, attributeValue));
		}
	}
}